<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>杨氏双缝干涉模拟</title>
    <!-- 引入PyScript库 -->
    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />
    <script defer src="https://pyscript.net/latest/pyscript.js"></script>
    <!-- 引入依赖库 -->
    <py-config>
        packages = ["numpy", "matplotlib"]
    </py-config>
    <style>
        body { padding: 20px; font-family: SimHei; }
        .container { max-width: 1200px; margin: 0 auto; }
        #plot-area { width: 100%; height: 800px; }
    </style>
</head>
<body>
    <div class="container">
        <h1>杨氏双缝干涉模拟</h1>
        <div id="plot-area"></div>
    </div>

    <py-script>
        import numpy as np
        import matplotlib.pyplot as plt
        from matplotlib.widgets import Slider, Button

        # 设置中文显示
        plt.rcParams['font.sans-serif'] = ['SimHei', 'WenQuanYi Micro Hei', 'Heiti TC']
        plt.rcParams["axes.unicode_minus"] = False

        def calculate_intensity(d, L, wavelength, bandwidth):
            x = np.linspace(-0.1, 0.1, 1000)
            theta = np.arctan(x / L)
            
            if bandwidth == 0:
                intensity = 4 * np.cos(np.pi * d * np.sin(theta) / wavelength) **2
            else:
                wavelengths = np.linspace(wavelength - bandwidth/2, wavelength + bandwidth/2, 5)
                total_intensity = np.zeros_like(x)
                for lam in wavelengths:
                    total_intensity += 4 * np.cos(np.pi * d * np.sin(theta) / lam)** 2
                intensity = total_intensity / len(wavelengths)
            
            delta_x = (wavelength * L) / d if d != 0 else 0
            k_max = int(0.08 / delta_x) if delta_x != 0 else 3
            k_values = np.arange(-k_max, k_max + 1)
            bright_positions = (k_values * wavelength * L) / d
            valid_mask = (bright_positions >= x.min()) & (bright_positions <= x.max())
            return x, intensity, delta_x, bright_positions[valid_mask], k_values[valid_mask]

        # 初始参数
        d_init = 0.5e-3  # 0.5mm
        L_init = 2.0      # 2米
        lambda_init = 632e-9  # 632nm
        bandwidth_init = 0e-9  # 0nm

        # 创建画布
        fig = plt.figure(figsize=(14, 8))

        # 图像区域
        ax_stripes = fig.add_axes([0.05, 0.55, 0.6, 0.4])
        ax_intensity = fig.add_axes([0.05, 0.1, 0.6, 0.4])

        # 控制区域
        ax_control = fig.add_axes([0.68, 0.1, 0.3, 0.85])
        ax_control.axis('off')
        ax_params = ax_control.inset_axes([0, 0.6, 1, 0.4])
        ax_params.axis('off')
        ax_sliders = ax_control.inset_axes([0, 0.15, 1, 0.45])
        ax_sliders.axis('off')
        ax_button = ax_control.inset_axes([0.2, 0, 0.6, 0.1])

        # 存储标注对象
        stripe_labels = []
        intensity_labels = []
        k0_line_stripes = None
        k0_line_intensity = None
        stripe_img = None
        intensity_line = None

        def init_plot():
            global k0_line_stripes, k0_line_intensity, stripe_labels, intensity_labels
            global stripe_img, intensity_line
            
            x, intensity, delta_x, bright_pos, k_vals = calculate_intensity(
                d_init, L_init, lambda_init, bandwidth_init
            )

            # 干涉条纹
            y = np.linspace(0, 0.1, 100)
            intensity_2d = np.tile(intensity[np.newaxis, :], (len(y), 1))
            stripe_img = ax_stripes.imshow(
                intensity_2d, cmap='gray', aspect='auto',
                extent=[x.min(), x.max(), y.min(), y.max()], origin='lower'
            )
            ax_stripes.set_title('杨氏双缝干涉条纹（竖直方向）')
            ax_stripes.set_ylabel('屏幕y坐标 (m)')
            ax_stripes.set_xlabel('屏幕x坐标 (m)')
            k0_line_stripes = ax_stripes.axvline(x=0, color='red', linestyle='--', linewidth=2, alpha=0.8)

            # 光强曲线
            intensity_line, = ax_intensity.plot(x, intensity, 'b-', linewidth=2)
            ax_intensity.set_title('光强分布曲线')
            ax_intensity.set_xlabel('屏幕x坐标 (m)')
            ax_intensity.set_ylabel('相对光强')
            ax_intensity.grid(True)
            ax_intensity.set_ylim(-0.1, 4.1)
            k0_line_intensity = ax_intensity.axvline(x=0, color='red', linestyle='--', linewidth=2, alpha=0.8)

            # 更新标注
            update_labels(bright_pos, k_vals)

        def update_labels(bright_positions, k_values):
            global stripe_labels, intensity_labels
            for label in stripe_labels:
                label.remove()
            for label in intensity_labels:
                label.remove()
            stripe_labels = []
            intensity_labels = []

            for x_pos, k in zip(bright_positions, k_values):
                label_text = f'k{k}' if k != 0 else 'k0 (中央明纹)'
                # 条纹图标注
                stripe_label = ax_stripes.text(
                    x_pos, 0.08, label_text,
                    ha='center', va='center',
                    bbox=dict(facecolor='white', alpha=0.7, boxstyle='round,pad=0.3')
                )
                stripe_labels.append(stripe_label)
                # 光强图标注
                intensity_label = ax_intensity.text(
                    x_pos, 3.8, label_text,
                    ha='center', va='center',
                    bbox=dict(facecolor='white', alpha=0.7, boxstyle='round,pad=0.3')
                )
                intensity_labels.append(intensity_label)

        # 创建滑块
        slider_height = 0.15
        slider_margin = 0.08
        ax_d = ax_sliders.inset_axes([0.1, 1 - slider_height - slider_margin * 0, 0.8, 0.1])
        d_slider = Slider(ax_d, '双缝间距 (mm)', 0.1, 2.0, valinit=d_init * 1e3)

        ax_L = ax_sliders.inset_axes([0.1, 1 - slider_height * 2 - slider_margin * 1, 0.8, 0.1])
        L_slider = Slider(ax_L, '缝屏距离 (m)', 0.5, 5.0, valinit=L_init)

        ax_lambda = ax_sliders.inset_axes([0.1, 1 - slider_height * 3 - slider_margin * 2, 0.8, 0.1])
        lambda_slider = Slider(ax_lambda, '波长 (nm)', 400, 700, valinit=lambda_init * 1e9)

        ax_bandwidth = ax_sliders.inset_axes([0.1, 1 - slider_height * 4 - slider_margin * 3, 0.8, 0.1])
        bandwidth_slider = Slider(ax_bandwidth, '带宽 (nm)', 0, 50, valinit=bandwidth_init * 1e9)

        def update(val):
            d = d_slider.val * 1e-3
            L = L_slider.val
            wavelength = lambda_slider.val * 1e-9
            bandwidth = bandwidth_slider.val * 1e-9

            x, intensity, delta_x, bright_pos, k_vals = calculate_intensity(d, L, wavelength, bandwidth)

            # 更新条纹
            y = np.linspace(0, 0.1, 100)
            intensity_2d = np.tile(intensity[np.newaxis, :], (len(y), 1))
            stripe_img.set_data(intensity_2d)
            stripe_img.set_extent([x.min(), x.max(), y.min(), y.max()])

            # 更新光强曲线
            intensity_line.set_xdata(x)
            intensity_line.set_ydata(intensity)
            ax_intensity.set_xlim(x.min(), x.max())

            # 更新标注
            update_labels(bright_pos, k_vals)

            # 更新参数文本
            monochromaticity = (bandwidth / wavelength) if wavelength != 0 else 0
            ax_params.clear()
            ax_params.axis('off')
            param_text = f"""
            实验参数：

            双缝间距: {d_slider.val:.1f} mm
            缝屏距离: {L_slider.val:.1f} m
            入射光波长: {lambda_slider.val:.0f} nm
            带宽: {bandwidth_slider.val:.0f} nm

            带宽说明：
            带宽表示光源波长的分布范围，0表示理想单色光，
            数值越大，单色性越差，条纹越模糊。

            计算结果：
            条纹间距△x: {delta_x * 1e3:.3f} mm
            光源单色性: {monochromaticity:.6f}
            """
            ax_params.text(0, 0.95, param_text, fontsize=10, verticalalignment='top', wrap=True)

            fig.canvas.draw_idle()

        def reset(event):
            d_slider.reset()
            L_slider.reset()
            lambda_slider.reset()
            bandwidth_slider.reset()

        # 绑定事件
        d_slider.on_changed(update)
        L_slider.on_changed(update)
        lambda_slider.on_changed(update)
        bandwidth_slider.on_changed(update)

        reset_button = Button(ax_button, '重置参数')
        reset_button.on_clicked(reset)

        # 初始化并显示
        init_plot()
        update(None)

        # 在网页中显示图像
        from matplotlib.backends.backend_agg import FigureCanvasAgg
        canvas = FigureCanvasAgg(fig)
        canvas.draw()
        buf = canvas.buffer_rgba()
        img_array = np.asarray(buf)

        from PIL import Image
        import io
        img = Image.fromarray(img_array)
        buf = io.BytesIO()
        img.save(buf, format='png')
        buf.seek(0)
        img_data = buf.read()

        from pyodide.ffi import to_js
        from js import Uint8Array, Blob, URL

        uint8_array = Uint8Array.new(to_js(img_data))
        blob = Blob.new([uint8_array], {type: 'image/png'})
        url = URL.createObjectURL(blob)

        img_element = document.createElement('img')
        img_element.src = url
        img_element.style.width = '100%'
        document.getElementById('plot-area').appendChild(img_element)
    </py-script>
</body>
</html>